﻿using System;
using System.Collections.Generic;
using System.Linq;
using Bots.DungeonBuddy.Actions;
using Bots.DungeonBuddy.Attributes;
using Bots.DungeonBuddy.Helpers;
using Styx;
using Styx.Helpers;
using Styx.Logic;
using Styx.Logic.Pathing;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;
using TreeSharp;
using Action = TreeSharp.Action;

namespace Bots.DungeonBuddy.Dungeons.Cataclysm
{
    public class Stonecore : Dungeon
    {
        #region Overrides of Dungeon

        /// <summary>
        ///   The Map Id of this dungeon. This is the unique id for dungeons thats used to determine which dungeon, the script belongs to
        /// </summary>
        /// <value> The map identifier. </value>
        public override uint DungeonId { get { return 307; } }


        public override WoWPoint Entrance { get { return new WoWPoint(1032.611f, 607.3205f, 160.9259f); } }

        public override bool IsFlyingCorpseRun { get { return true; } }

        readonly CircularQueue<WoWPoint> _corpseRun = new CircularQueue<WoWPoint>
                             {
                                 new WoWPoint(934.2088, 638.4651, 171.1208),
                                 new WoWPoint(1024.311, 643.19, 162.269),
                                 new WoWPoint(1032.611f, 607.3205f, 160.9259f)
                             };

        public override CircularQueue<WoWPoint> CorpseRunBreadCrumb
        {
            get { return _corpseRun; }
        }

        /// <summary>
        ///   IncludeTargetsFilter is used to add units to the targeting list. If you want to include a mob thats usually removed by the default filters, you shall use that.
        /// </summary>
        /// <param name="incomingunits"> Units passed into the method </param>
        /// <param name="outgoingunits"> Units passed to the targeting class </param>
        public override void IncludeTargetsFilter(List<WoWObject> incomingunits, HashSet<WoWObject> outgoingunits)
        {
            foreach (WoWObject obj in incomingunits)
            {
                var unit = obj as WoWUnit;
                if (unit == null)
                    continue;
                if (unit.Entry == 42695 && unit.Combat) // Stonecore Sentry
                    outgoingunits.Add(unit);
            }
        }

        /// <summary>
        ///   RemoveTargetsFilter is used to remove units thats not wanted in target list. Like immune mobs etc.
        /// </summary>
        /// <param name="units"> </param>
        public override void RemoveTargetsFilter(List<WoWObject> units)
        {
            units.RemoveAll(
                o =>
                {
                    var unit = o as WoWUnit;

                    if (unit == null)
                        return false;

                    // for Corborus when he submerges
                    if (unit.Entry == 43438 && unit.HasAura("Submerge"))
                        return true;

                    // For Ozruk fight. Ranged dps should stop dpsing if the boss has spell reflect buff
                    if (StyxWoW.Me.IsRange() && StyxWoW.Me.IsDps() && unit.HasAura("Elementium Bulwark") && unit.DistanceSqr > 8 * 8)
                        return true;

                    // For Priestess fight. It becomes immune with this shield on
                    if (unit.HasAura("Energy Shield"))
                        return true;

                    return false;
                });
        }

        /// <summary>
        ///   WeighTargetsFilter is used to weight units in the targeting list. If you want to give priority to a certain npc, you should use this method.
        /// </summary>
        /// <param name="units"> </param>
        public override void WeighTargetsFilter(List<Targeting.TargetPriority> units)
        {
            foreach (var t in units)
            {
                var prioObject = t.Object;
                var unit = prioObject as WoWUnit;
                if (unit == null)
                    continue;
                // Stonecore Sentry. kill it before it runs for help.
                if (unit.Entry == 42695 && unit.Combat && !unit.IsTargetingMeOrPet && !unit.IsTargetingMyPartyMember) 
                    t.Score += 500;
                // Devout Follower. try to pick up aggro.
              //  if (StyxWoW.Me.IsTank() && unit.Entry == 42428 && unit.Combat && !unit.IsTargetingMeOrPet) 
                //    t.Score += 500;
            }
        }

        #endregion

        #region Encounter Handlers

        public Composite RootBehavior()
        {
            return new PrioritySelector(
                ScriptHelpers.CreateRunAwayFromBad(ctx => true, 8, 80654, 204337) // Stalactite - falling rock
                );
        }

        /// <summary>
        /// BossEntry is the Entry of the boss unit. (WoWUnit.Entry) BossName is optional. Its there just to make it easier to find which boss that composite belongs to. The context of the encounter composites is the Boss as WoWUnit
        /// </summary>
        /// <returns></returns>
        [EncounterHandler(43438, "Corborus")] // 43743, "Thrashing Charge
        public Composite CorborusFight()
        {
            WoWUnit boss = null;
            return new PrioritySelector(
                ctx => boss = ctx as WoWUnit,
                ScriptHelpers.CreateRunAwayFromBad(ctx => true, 20, 43743), // Thrashing Charge. This is the rubble that apears when Corborus submerges.
                ScriptHelpers.CreateRunAwayFromBad(ctx => true, 6, 86881, 92648) // Crystal Barrage
                );
        }


        [EncounterHandler(42692, " Stonecore Bruiser")]
        public Composite  StonecoreBruiserEncounter()
        {
            return new PrioritySelector(
                ScriptHelpers.CreateTankFaceAwayGroupUnit(15));
        }

        [EncounterHandler(43214, "Slabhide")]
        public Composite SlabhideFight()
        {
            WoWUnit boss = null;
            WoWGameObject stalactite = null;

            return new PrioritySelector(
                ctx =>
                {
                    boss = (WoWUnit)ctx;
                    stalactite =
                        ObjectManager.GetObjectsOfType<WoWGameObject>().Where(
                            g => g.Entry == 204337 && Math.Abs(g.Z - StyxWoW.Me.Z) < 7 && g.Location.DistanceSqr(boss.Location) > 15 * 15).OrderBy(g => g.DistanceSqr).FirstOrDefault();
                    return boss;
                },
                ScriptHelpers.CreateRunAwayFromBad(ctx => true, 8, 43242), // Lava Fissure
                // run los at a stalactite when boss is casting Crystal storm
                ScriptHelpers.CreateLosLocation(ctx => boss.CastingSpellId == 92265 && stalactite != null, () => boss.Location, () => stalactite.Location, () => 5),
                // only tank should be in front when boss is not flying to avoid the Sand Blast abiliy
                new Decorator(
                    ctx => Math.Abs(boss.Z - StyxWoW.Me.Z) < 7,
                    new PrioritySelector(
                        // move to boss location.
                        new Decorator(ctx => StyxWoW.Me.IsHealer && !ScriptHelpers.Tank.InLineOfSpellSight,
                            new Sequence(
                                new ActionLogger("Tank is not in LOS, moving to boss"),
                                new Action(ctx => Navigator.MoveTo(boss.Location)))),
                        ScriptHelpers.CreateTankFaceAwayGroupUnit(17),
                        ScriptHelpers.GetBehindUnit(ctx => !StyxWoW.Me.IsTank() && boss.IsSafelyFacing(StyxWoW.Me, 65), () => boss)))
                );
        }

        [EncounterHandler(42188, "Ozruk", Mode = CallBehaviorMode.CurrentBoss)]
        public Composite OzrukFight()
        {
            WoWUnit boss = null;
            List<WoWUnit> trash = null;
            var trashLoc = new WoWPoint(1532.98, 1163.521, 217.8522);
            var trashTankLoc = new WoWPoint(1513.444, 1177.482, 221.716);
            var trashWaitLoc = new WoWPoint(1531.079, 1183.519, 219.5639);

            var shatterTimer = new WaitTimer(TimeSpan.FromSeconds(6));
            return new PrioritySelector(ctx => boss = ctx as WoWUnit,
                new Decorator(ctx => boss != null && boss.Combat,
                    new PrioritySelector(
                        new Action(ctx =>
                                       {
                                           if (IsCastingGroundSlam(boss))
                                               shatterTimer.Reset();
                                           return RunStatus.Failure;
                                       }),
                // run out when boss is casting shatter or right after he casts ground slam.
                        ScriptHelpers.CreateRunAwayFromBad(ctx => IsCastingShatter(boss) || !shatterTimer.IsFinished, 17, 42188),
                // run away from ground slamp impact location.
                        ScriptHelpers.CreateRunAwayFromLocation(ctx => IsCastingGroundSlam(boss), 8, () => WoWMathHelper.GetPointAt(boss.Location, 6, boss.Rotation, 0)))),

                new Decorator(ctx => boss == null || !boss.Combat,
                    new PrioritySelector(ctx => trash = ScriptHelpers.GetUnfriendlyNpsAtLocation(() => trashLoc, 30),
                            ScriptHelpers.CreatePullNpcToLocation(ctx => trash != null && trash.Any() && !StyxWoW.Me.Combat, ctx => true, () => trash[0], () => trashTankLoc, () => trashWaitLoc, 10)
                        ))
                );
        }

        bool IsCastingGroundSlam(WoWUnit unit) { return unit != null && (unit.CastingSpellId == 78903 || unit.CastingSpellId == 92410); }

        bool IsCastingShatter(WoWUnit unit) { return unit != null && (unit.CastingSpellId == 78807 || unit.CastingSpellId == 92662); }

        [EncounterHandler(42333, "High Priestess Azil", Mode = CallBehaviorMode.Proximity, BossRange = 100)]
        public Composite HighPriestessAzilFight()
        {
            WoWUnit boss = null;
            List<WoWUnit> trash = null;
            var trashLoc = new WoWPoint(1327.616, 990.7188, 207.9859);
            var trashTankLoc = new WoWPoint(1294.641, 1007.01, 208.957);
            var trashWaitLoc = new WoWPoint(1292.202, 1013.337, 209.1433);

            return new PrioritySelector(
                ctx =>
                {
                    trash = ScriptHelpers.GetUnfriendlyNpsAtLocation(() => trashLoc, 30, u => u.Entry == 42428); // Devout Follower
                    return boss = ctx as WoWUnit;
                },
                ScriptHelpers.CreateRunAwayFromBad(ctx => true, 10, 42499), // Gravity Well
                ScriptHelpers.CreateRunAwayFromBad(ctx => boss.HasAura("Energy Shield"), 10, 42333),
                ScriptHelpers.CreateRunAwayFromBad(ctx => true, 12, 80511), // Seismic Shard Targeting
                // run from aggro.
                ScriptHelpers.CreateRunAwayFromBad(ctx => !StyxWoW.Me.IsTank() && trash.Any(u => u.IsTargetingMeOrPet), 20, o => trash != null && o == trash.FirstOrDefault(u => u.IsTargetingMeOrPet)),
                ScriptHelpers.CreatePullNpcToLocation(ctx => trash.Any() && !boss.Combat, ctx => true, () => trash[0], () => trashTankLoc, () => trashWaitLoc, 10),
                new Decorator(ctx => StyxWoW.Me.IsTank(),
                    ScriptHelpers.CreatePullNpcToLocation(ctx => !trash.Any() && !boss.Combat, ctx => true, () => boss, () => trashTankLoc, null, 0))
                );
        }

        readonly WaitTimer _teleportTimer = new WaitTimer(TimeSpan.FromSeconds(10));
        public override MoveResult MoveTo(WoWPoint location)
        {
            // use entrance portal.
            if ((StyxWoW.Me.X < 900 && location.X > 1200 || StyxWoW.Me.Y > 1200 && location.Y < 900) && _teleportTimer.IsFinished)
            {
                var teleporter = ObjectManager.GetObjectsOfType<WoWUnit>().FirstOrDefault(o => (o.Entry == 51396 || o.Entry == 51397) && o.HasAura("Teleporter Active Visual"));
                if (teleporter != null)
                {
                    if (!teleporter.WithinInteractRange)
                        return Navigator.MoveTo(teleporter.Location);
                    teleporter.Interact();
                    _teleportTimer.Reset();
                    return MoveResult.Moved;
                }
            }
            return MoveResult.Failed;
        }
        #endregion
    }
}